package com.demo.cryptology.asymmetric;


import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 *  RSA加密算法是一种非对称加密算法。
 * 在公钥加密标准和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特（Ron Rivest）、阿迪·萨莫尔（Adi Shamir）和伦纳德·阿德曼（Leonard Adleman）一起提出的。当时他们三人都在麻省理工学院工作。
 * RSA就是他们三人姓氏开头字母拼在一起组成的。
 * RSA算法的可靠性基于分解极大的整数是很困难的。
 * 假如有人找到一种很快的分解因子的算法的话，那么用RSA加密的信息的可靠性就肯定会极度下降。
 * 但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式解破。
 * 到2008年为止，世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长，用RSA加密的信息实际上是不能被解破的。
 * RSA算法利用两个很大的质数相乘所产生的乘积来加密。
 * 这两个质数无论哪一个先与原文件编码相乘，对文件加密，均可由另一个质数再相乘来解密。
 * 但要用一个质数来求出另一个质数，则是十分困难的。因此将这一对质数称为密钥对(Key Pair)。
 * 在加密应用时，某个用户总是将一个密钥公开，让需发信的人员将信息用其公共密钥加密后发给该用户，而一旦信息加密后，只有用该用户一个人知道的私用密钥才能解密。
 * 具有数字凭证身份的人员的公共密钥可在网上查到，亦可在请对方发信息时主动将公共密钥传给对方，这样保证在Internet上传输信息的保密和安全。
 */
public class RSA {
    private static String RSA = "RSA";


    private static final String CIPHER_INSTANCE = "RSA/ECB/PKCS1Padding";

    static final Base64.Decoder decoder = Base64.getDecoder();
    static final Base64.Encoder encoder = Base64.getEncoder();

    public static Map<String, String> getKeys(int len) throws NoSuchAlgorithmException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
        kpg.initialize(len);
        KeyPair keyPair = kpg.generateKeyPair();
        Key publicKey = keyPair.getPublic();
        Key privateKey = keyPair.getPrivate();
        String pubKey = encoder.encodeToString(publicKey.getEncoded());
        String priKey = encoder.encodeToString(privateKey.getEncoded());
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("publicKey", pubKey);
        resultMap.put("privateKey", priKey);
        return resultMap;
    }


    public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoder.decode(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(keySpec);
        return key;
    }


    public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoder.decode(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        return key;
    }

    private static final String CHARSET_UTF_8 = "UTF-8";

    public static String publicEncrypt(String data, RSAPublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return encoder.encodeToString(rsaCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET_UTF_8), publicKey.getModulus().bitLength()));
    }


    public static String privateDecrypt(String data, RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(rsaCodec(cipher, Cipher.DECRYPT_MODE, decoder.decode(data), privateKey.getModulus().bitLength()), CHARSET_UTF_8);
    }


    public static String privateEncrypt(String data, RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return encoder.encodeToString(rsaCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET_UTF_8), privateKey.getModulus().bitLength()));
    }

    public static String publicDecrypt(String data, RSAPublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return new String(rsaCodec(cipher, Cipher.DECRYPT_MODE, decoder.decode(data), publicKey.getModulus().bitLength()));
    }


    private static byte[] rsaCodec(Cipher cipher, int opmode, byte[] data, int keySize) throws Exception {
        int maxBlock = 0;
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        while (data.length > offSet) {
            if (data.length - offSet > maxBlock) {
                buff = cipher.doFinal(data, offSet, maxBlock);
            } else {
                buff = cipher.doFinal(data, offSet, data.length - offSet);
            }
            out.write(buff, 0, buff.length);
            i++;
            offSet = i * maxBlock;
        }
        byte[] resultData = out.toByteArray();
        out.close();
        return resultData;
    }


    public static void main(String[] args) throws Exception {
        Map<String, String> keyMap = getKeys(1024);
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        System.out.println("公钥:\n" + publicKey);
        System.out.println("私钥:\n" + privateKey);

        String data = "打开了发动机卡拉决定翻开了解放法赛季的开罗附近阿克萨拉丁教父卡角色快乐的房间卡拉斯决定开罗附近阿斯克里的解放卡拉斯酒店客房法思考的解放卡拉祭祀坑的反抗拉斯蒂芬巴角色等级阿vjaklvlzxjlcvjlkx 卡拉角色的开罗附近阿喀琉斯的发送到解放卡角色的看法";
        System.out.println("明文:\n" + data);
        System.out.println("明文大小:\n" + data.getBytes().length);

        String encodeData = publicEncrypt(data, getPublicKey(publicKey));
        System.out.println("密文:\n" + encodeData);
        String decodeData = privateDecrypt(encodeData, getPrivateKey(privateKey));
        System.out.println("解密:\n" + decodeData);


    }


}
